/**
 *  Copyright 2007-2008 University Of Southern California
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing,
 *  software distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package edu.isi.pegasus.planner.selector.site;



import edu.isi.pegasus.planner.classes.Job;


import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;

/**
 * This ends up scheduling the jobs in a round robin manner. In order to avoid
 * starvation, the jobs are scheduled in a round robin manner per level, and
 * the queue is initialised for each level.
 *
 * @author Karan Vahi
 * @version $Revision: 2590 $
 */

public class RoundRobin
    extends AbstractPerJob {

    /**
     * The current level in the abstract workflow. It is level that is designated
     * by Chimera while constructing the graph bottom up.
     */
    private int mCurrentLevel;

    /**
     * The list of pools that have been given by the user at run time or has been
     * authenticated against. At present these are the same as the list of pools
     * that is passed for site selection at each function.
     */
    private java.util.LinkedList mExecPools;


    /**
     * The default constructor. Not to be used.
     */
    public RoundRobin() {
        mCurrentLevel = -1;
    }


    /**
     * Returns a brief description of the site selection techinque implemented by
     * this class.
     *
     * @return String
     */
    public String description() {
        String st = "Round Robin Scheduling per level of the workflow";
        return st;
    }


    /**
     * Maps a job in the workflow to an execution site.
     *
     * @param job    the job to be mapped.
     * @param sites  the list of <code>String</code> objects representing the
     *               execution sites that can be used.
     *
     */
    public void mapJob( Job job, List sites ){
        ListIterator it;
        NameValue current;
        NameValue next;

        if ( mExecPools == null ) {
            initialiseList( sites );
        }

        if ( job.level != mCurrentLevel ) {
            //reinitialize stuff
            mCurrentLevel = job.level;
            it = mExecPools.listIterator();
            while ( it.hasNext() ) {
                ( ( NameValue ) it.next() ).setValue( 0 );
            }
        }

        //go around the list and schedule it to the first one where it can
        it = mExecPools.listIterator();
        String mapping = null;
        while ( it.hasNext() ) {
            current = ( NameValue ) it.next();
            //check if job can run on pool
            if ( mTCMapper.isSiteValid( job.namespace, job.logicalName,
                job.version, current.getName() ) ) {
                mapping = current.getName();
                //update the the number of times used and place it at the
                //correct position in the list
                current.increment();

                //the current element stays at it's place if it is the only one
                //in the list or it's value is less than the next one.
                if ( it.hasNext() ) {
                    next = ( NameValue ) it.next();
                    if ( current.getValue() <= next.getValue() ) {
                        break;
                    } else {
                        current = ( NameValue ) it.previous();
                        current = ( NameValue ) it.previous();
                        System.out.print( "" );
                    }
                }
                it.remove();

                //now go thru the list and insert in the correct position
                while ( it.hasNext() ) {
                    next = ( NameValue ) it.next();

                    if ( current.getValue() <= next.getValue() ) {
                        //current has to be inserted before next
                        next = ( NameValue ) it.previous();
                        it.add( current );
                        break;
                    }
                }
                //current goes to the end of the list
                it.add( current );
                break;
            }
        }

        //means no pool has been found to which the job could be mapped to.
        job.setSiteHandle(  mapping );
    }



    /**
     * It initialises the internal list. A node in the list corresponds to a pool
     * that can be used, and has the value associated with it which is the
     * number of jobs in the current level have been scheduled to it.
     *
     * @param pools List
     */
    private void initialiseList( List pools ) {
        if ( mExecPools == null ) {
            mExecPools = new java.util.LinkedList();

            Iterator it = pools.iterator();
            while ( it.hasNext() ) {
                mExecPools.add( new NameValue( ( String ) it.next(), 0 ) );
            }
        }
    }



    /**
     * A inner name value class that associates a string with an int value.
     * This is used to populate the round robin list that is used by this
     * scheduler.
     */
    class NameValue {
        /**
         * Stores the name of the pair (the left handside of the mapping).
         */
        private String name;

        /**
         * Stores the corresponding value to the name in the pair.
         */
        private int value;

        /**
         * The default constructor  which initialises the class member variables.
         */
        public NameValue() {
            name = new String();
            value = -1;
        }

        /**
         * Initialises the class member variables to the values passed in the
         * arguments.
         *
         * @param name  corresponds to the name in the NameValue pair
         *
         * @param value corresponds to the value for the name in the NameValue pair
         */
        public NameValue( String name, int value ) {
            this.name = name;
            this.value = value;
        }

        /**
         * The set method to set the value.
         * @param value int
         */
        public void setValue( int value ) {
            this.value = value;
        }

        /**
         * Returns the value associated with this pair.
         * @return int
         */
        public int getValue() {
            return this.value;
        }

        /**
         * Returns the key of this pair, i.e the left hand side of the mapping.
         * @return String
         */
        public String getName() {
            return this.name;
        }

        /**
         * Increments the int value by one.
         */
        public void increment() {
            value += 1;
        }

        /**
         * Returns a copy of this object.
         * @return Object
         */
        public Object clone() {
            NameValue nv = new NameValue( this.name, this.value );
            return nv;

        }

        /**
         * Writes out the contents of the class to a String in form suitable for
         * displaying.
         * @return String
         */
        public String toString() {
            String str = name + "-->" + value;

            return str;
        }

    }
}
